// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::{Path, PathBuf};

use anyhow::Context;
use clap::ValueEnum;
use log::info;
use nix::unistd::Uid;
use tokio::{fs::OpenOptions, io::AsyncWriteExt, process::Command};

use crate::utils::{confirm, git_clone_cmd, random_tmp_path};

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ShellConfig {
    Bash,
    Mksh,
}

impl ShellConfig {
    fn path(&self) -> PathBuf {
        match self {
            ShellConfig::Bash => dirs::home_dir().unwrap_or(PathBuf::from("~")).join(".bashrc"),
            ShellConfig::Mksh => dirs::home_dir().unwrap_or(PathBuf::from("~")).join(".mkshrc"),
        }
    }

    fn generate(
        &self,
        config_root: &Path,
        pkgs_root: &Path,
        cache_root: &Path,
    ) -> String {
        format!("\n\
            # >>> ferrum configuration >>>\n\
            export CONFIG_ROOT={}\n\
            export PKGS_ROOT={}\n\
            export CACHE_ROOT={}\n\
            # <<< ferrum configuration <<<\n\
            ",
            config_root.display(),
            pkgs_root.display(),
            cache_root.display(),
        )
    }
}

pub async fn init_system_config(
    config_root: impl AsRef<Path>,
    pkgs_root: impl AsRef<Path>,
    cache_root: impl AsRef<Path>,
    shell_configs: &[ShellConfig],
    local_config: Option<PathBuf>,
    force: bool,
) -> anyhow::Result<()> {
    let config_root = std::path::absolute(config_root.as_ref())?;
    let pkgs_root = std::path::absolute(pkgs_root.as_ref())?;
    let cache_root = std::path::absolute(cache_root.as_ref())?;
    let home_dir = dirs::home_dir().context("failed to get home dir")?;

    init_pkgs_root(&pkgs_root, force, &home_dir).await?;
    init_config_root(&config_root, local_config, force, &home_dir).await?;
    
    for shell_config in shell_configs {
        let config_path = shell_config.path();
        info!("Generating shell config to {}...", config_path.display());

        let config_content = shell_config.generate(&config_root, &pkgs_root, &cache_root);
        OpenOptions::new()
            .create(true)
            .append(true)
            .open(&config_path)
            .await
            .context(format!("failed to open {}", config_path.display()))?
            .write_all(config_content.as_bytes())
            .await
            .context(format!("failed to write to {}", config_path.display()))?;
    }

    Ok(())
}

async fn init_pkgs_root(pkgs_root: &Path, force: bool, home_dir: &Path) -> anyhow::Result<()> {
    info!("Creating pkgs root directory {}...", pkgs_root.display());

    if !pkgs_root.exists()
        || force
        || confirm(&format!("{} already exists, do you want to overwrite it?", pkgs_root.display()))
            .context("failed to read input")?
    {
        if pkgs_root.exists() {
            rm_with_sudo(&pkgs_root, &home_dir).await?;
        }

        let need_sudo = mkdir_with_sudo(&pkgs_root, &home_dir).await?;
        if need_sudo {
            let (mut chown_cmd, _) = command_with_sudo("chown", &pkgs_root, &home_dir);
            chown_cmd
                .arg("-R")
                .arg(Uid::current().to_string())
                .arg(&pkgs_root);
            if !chown_cmd
                .status()
                .await?
                .success()
            {
                return Err(anyhow::anyhow!("failed to chown {} to {}", pkgs_root.display(), Uid::current()));
            }
        }
    }
    Ok(())
}

async fn init_config_root(config_root: &Path, local_config: Option<PathBuf>, force: bool, home_dir: &Path) -> anyhow::Result<()> {
    const FERRIUM_PKGS_URL: &str = "https://atomgit.com/m-robots/robot_tools_ferrium_pkgs";

    info!("Initializing configs in {}...", config_root.display());

    if !config_root.exists()
        || force
        || confirm(&format!("{} already exists, do you want to overwrite it?", config_root.display()))
        .context("failed to read input")?
    {
        if config_root.exists() {
            rm_with_sudo(&config_root, &home_dir).await?;
        }

        if let Some(local_config) = local_config {
            cp_with_sudo(
                local_config,
                config_root,
                &home_dir,
                true,
            ).await?;
        } else {
            let tmp_dir = random_tmp_path::<3>()
                .context("failed to create temp dir")?;
            let mut clone_cmd = git_clone_cmd(FERRIUM_PKGS_URL, tmp_dir.as_path(), false);
            info!("cloning {} to {}", FERRIUM_PKGS_URL, tmp_dir.display());
            let status = clone_cmd.status().await?;
            if !status.success() {
                return Err(anyhow::anyhow!("failed to clone {} to {}", FERRIUM_PKGS_URL, tmp_dir.display()));
            }

            mkdir_with_sudo(&config_root, &home_dir).await?;

            cp_with_sudo(
                tmp_dir.join("config/config.toml"),
                config_root.join("config.toml"),
                &home_dir,
                false,
            ).await?;
            for toolchain in ["ohos-cross", "linux-musl-cross"] {
                cp_with_sudo(
                    tmp_dir.join("config").join(toolchain),
                    config_root.join(toolchain),
                    &home_dir,
                    true,
                ).await?;
            }

            cp_with_sudo(
                tmp_dir.join("pkgs"),
                config_root.join("pkgs"),
                home_dir,
                true,
            ).await?;
            
            tokio::fs::remove_dir_all(&tmp_dir)
                .await
                .context(format!("failed to remove temp dir {}", tmp_dir.display()))?;
        }
    }
    Ok(())
}

fn command_with_sudo(cmd: &str, check_path: &Path, home_dir: &Path) -> (Command, bool) {
    if check_path.starts_with(home_dir) || Uid::current().is_root() {
        (Command::new(cmd), false)
    } else {
        let mut command = Command::new("sudo");
        command.arg(cmd);
        (command, true)
    }
}

async fn mkdir_with_sudo(path: impl AsRef<Path>, home_dir: impl AsRef<Path>) -> anyhow::Result<bool> {
    let path = path.as_ref();
    let home_dir = home_dir.as_ref();
    let (mut cmd, need_sudo) = command_with_sudo("mkdir", path, home_dir);
    cmd.arg("-p");
    cmd.arg(path);
    let status = cmd.status().await?;
    if !status.success() {
        return Err(anyhow::anyhow!("failed to create {} directory", path.display()));
    }
    Ok(need_sudo)
}

async fn rm_with_sudo(path: impl AsRef<Path>, home_dir: impl AsRef<Path>) -> anyhow::Result<()> {
    let path = path.as_ref();
    let home_dir = home_dir.as_ref();
    let (mut cmd, _) = command_with_sudo("rm", path, home_dir);
    cmd.arg("-rf");
    cmd.arg(path);
    let status = cmd.status().await?;
    if !status.success() {
        return Err(anyhow::anyhow!("failed to remove {}", path.display()));
    }
    Ok(())
}

async fn cp_with_sudo(
    src: impl AsRef<Path>,
    dst: impl AsRef<Path>,
    home_dir: impl AsRef<Path>,
    recursive: bool,
) -> anyhow::Result<()> {
    let src = src.as_ref();
    let dst = dst.as_ref();
    let home_dir = home_dir.as_ref();
    let mut cmd = command_with_sudo("cp", dst, home_dir).0;
    cmd.arg("-P");
    if recursive {
        cmd.arg("-r");
    }
    let status = cmd
        .arg(src)
        .arg(dst)
        .status()
        .await?;
    if !status.success() {
        return Err(anyhow::anyhow!("failed to copy {} to {}", src.display(), dst.display()));
    }
    Ok(())
}
